DSS: Streamlining Invoice Processing¶

Document Understanding Transformer for Efficient Business Operations in Python¶

Inclass: Data Science Series - Streamlining Invoice Processing
  • 💻 Detail Material: Document Understanding Transformer for Efficient Business Operations in Python
  • 👩🏻‍🏫 Instructor: Fiqey Indriati Eka Sari

© Algoritma 2024


Background¶

Di dalam lanskap operasional bisnis yang terus berkembang dengan cepat, menguasai pemrosesan faktur yang efektif menjadi sangat penting. Pemrosesan faktur adalah bagian kritis dari kegiatan bisnis yang melibatkan pengelolaan keuangan dan akuntansi. Faktur yang diproses dengan baik membantu dalam pelacakan pengeluaran, mengoptimalkan arus kas, dan memastikan kepatuhan terhadap peraturan keuangan. Dengan lanskap bisnis yang cepat berubah, perusahaan perlu dapat beradaptasi dengan cepat. Menguasai pemrosesan faktur menjadi penting untuk mengatasi ketidakpastian dan menjaga kecepatan tanggapan terhadap perubahan di sekitarnya.

Materi ini menawarkan solusi komprehensif dengan workshop khusus yang dilengkapi dengan keterampilan terkini. Fokusnya adalah pada penggunaan Document Understanding Transformer, sebuah model deep learning NLP, dengan pemrograman Python. Dengan menyelami teknologi ini, yang terbukti berhasil dalam tugas pemrosesan bahasa alami, peserta akan memperoleh keahlian untuk menyederhanakan dan meningkatkan alur kerja pemrosesan faktur.

Output¶

Setelah menyelesaikan workshop ini, Anda akan dapat:

  • Memanfaatkan Document Understanding Transformer (Donut) untuk ekstraksi data yang akurat dari gambar penerimaan, mengoptimalkan alur kerja pemrosesan faktur.
  • Meningkatkan keterampilan analisis data untuk pengambilan keputusan yang terinformasi dalam operasional bisnis dengan memanfaatkan hasil keluaran dari Document Understanding Transformer.
  • Membangun aplikasi web yang ramah pengguna dengan alat seperti Gradio untuk pemindaian efisien penerimaan dan faktur, secara mulus sejalan dengan kebutuhan bisnis modern untuk proses keuangan yang efisien.

🎯 Training Objectives¶

Python Programming Basics

  • Anaconda Environment Preparation
  • Working with Jupyter Notebook (.ipynb)
  • Understanding Fundamental of Variables & Python Data Types
  • Exploring Python Functions and Looping Concepts

Working with DataFrame Pandas

  • Understanding Dataframe pandas
  • Master Data Manipulation and Pre-processing
  • Perform Conditional Subsetting Operations

Document Understanding Transformer (Donut) Utilization

  • Introduction to 🍩 OCR-free Document Understanding Transformer (Donut) Model
  • Document Information Extraction using Donut
  • Data Analysis Enhancement with Donut Outputs

Integration Gradio for Efficient Scanning

  • Understand the Fundamentals of Gradio
  • Develop Gradio for Efficient Receipt and Invoice Scanning

Python Programming Basics¶

Anaconda Environment Preparation¶

Cara Membuat Environtment Baru:¶

  • 1️⃣ Membuka Terminal pada Visual Studio Code

    Saat pertama kali kita membuka Visual Studio Code, terdapat beberapa menu di pojok kiri atas. Silahkan kamu klik menu Terminal pada bagian kiri atas. Lalu, pilih New Terminal. Selesai, kamu berhasil membuka terminal baru.

  • 2️⃣ Buat Environment dengan Nama ENV_NAME:

    conda create -n dss_invoice python=3.10

    Tujuan dari kita menuliskan python=3.10 agar python yang terinstall pada virtual environtment yang kita buat adalah python dengan versi 3.10.

  • 3️⃣ Mengaktifkan Environment

    Setelah virtual environtment yang baru kita buat sudah selesai, kita bisa mengaktifkan environtment yang sudah kita buat dengan code berikut.

    conda activate dss_invoice

Install/Update Package¶

Sebelum melakukan instalasi package, pastikan Anda sudah melakukan aktivasi virtual environment tempat Anda mau menginstall package. pip install <PACKAGE_NAME>

Kita juga bisa menginstallnya melalui sebuah teks reqirements.txt yang berisi list packages yang perlu diinstall, commandnya pip install -r requirements.txt dengan syarat sebelum menjalankan command ini, kita sudah berapa pada folder material utama

🧪 Knowledge Check¶

Pilihlah jawaban yang tepat dengan memberikan tanda centang pada kotak.

  1. Berikut adalah tujuan kita membuat virtual environment, KECUALI ...

    • Kolaborasi (sharing environment)
    • Mengisolasi package beserta versi-nya
    • Virtual environment harus dibuat agar Python dapat dijalankan
  2. Pasangkan beberapa opsi ini dengan pilihan yang cocok:

    • Visual Studio Code: ...
    • Miniconda: ...
    • Python: ...

    Pilihan Jawaban:

    • a. Bahasa Pemrograman
    • b. Environment Manager
    • c. Integrated Development Environment (IDE)

Working with Jupyter Notebook (.ipynb)¶

Markdown Cell dan Code Cell¶

Tipe cell dalam notebook:

1️⃣ Markdown : untuk menuliskan narasi

Ini adalah cell markdown. Kita bisa menulis teks bold, italic, bahkan formula matematis seperti:

\begin{equation} f(x) = \frac{e^{-x}}{(1+e^{-x})} \end{equation}

Cara menambahkan gambar:

<img src="..." width="500px"></img>

2️⃣ Code : untuk menuliskan script code

💡 symbol # pada cell code berarti adalah sebuah comment. Comment pada cell code tidak akan dieksekusi

In [ ]:
# ini merupakan cell untuk code
print("dan ini adalah cell code tempat menuliskan code python")
dan ini adalah cell code tempat menuliskan code python

Untuk run cell code: Ctrl + Alt + Enter

Command Mode and Edit Mode¶

Ada 2 mode cell dalam notebook:

  1. 1️⃣ Command Mode

    • a : Menambah cell baru di atas
    • b : Menambah cell baru di bawah
    • d + d : Menghapus cell terpilih
    • c : Menyalin cell terpilih
    • v : Paste cell terpilih
    • m : Mengubah tipe cell ke markdown
    • y : Mengubah tipe cell ke kode
    • enter : enter Edit Mode
  2. 2️⃣ Edit Mode (Cell Terdapat Border Biru Persegi Panjang)

    • Ctrl + Enter: eksekusi satu cell
    • Esc: mengubah edit mode menjadi command mode

Understanding Fundamental of Variables & Python Data Types¶

Variables¶

Variable adalah sebuah nama yang dipakai untuk menyimpan sebuah nilai.

So if we say, data is food, then variable is where we store the food.

💡 Tanda = dipakai untuk assignment dalam membuat variabel baru

In [ ]:
angka = 10
angka
Out[ ]:
10

🚀 Mari kita coba buat sebuah objek, yang berisikan nama dss ini!

In [ ]:
dss_name = "Data Science Series: Streamlining Invoice Processing"
print(dss_name)
Data Science Series: Streamlining Invoice Processing
💡 Sebagai catatan, seperti bahasa pemrograman yang lainnya, Python bersifat case-sensitive.

Sehingga dss_name dan DSS_Name dimaknai berbeda sehingga akan dianggap variabel yang berbeda pula. Maka dari itu, penamaan variable menjadi hal yang perlu diperhatikan.

In [ ]:
## code here
dss_name == dss_name
Out[ ]:
True

Kode di atas mengembalikan True sebagai output. Cobalah untuk membuat variabel baru dan gunakan True sebagai namanya. kemudian lihat apa yang akan terjadi.

SyntaxError: can't assign to keyword


Sebagai catatan, True, dan juga lawannya, False termasuk ke dalam daftar kata yang dinamakan Python Keywords. Kita tidak dapat menggunakan keyword sebagai nama variabel ataupun sebagai fungsi.

Semua python keyword selain True, False, dan None adalah huruf kecil.


✨ Keywords adalah kata kunci yang sudah ditetapkan oleh Python sebagai nama yang tidak bisa dipakai baik untuk penamaan fungsi, variabel, dan lainnya. Keyword ditulis dalam lower-case (huruf kecil semua) kecuali keyword True, False, dan None. Sejauh ini (Python 3.10) keyword yang ada pada Python adalah sebagai berikut:

In [ ]:
# cek daftar keyword
import keyword
keyword.kwlist
Out[ ]:
['False',
 'None',
 'True',
 'and',
 'as',
 'assert',
 'async',
 'await',
 'break',
 'class',
 'continue',
 'def',
 'del',
 'elif',
 'else',
 'except',
 'finally',
 'for',
 'from',
 'global',
 'if',
 'import',
 'in',
 'is',
 'lambda',
 'nonlocal',
 'not',
 'or',
 'pass',
 'raise',
 'return',
 'try',
 'while',
 'with',
 'yield']

⚠️ Berikut beberapa ketentuan dalam memberikan nama variable pada Python:

  • Tidak boleh menggunakan angka di awal. Misal 1Algoritma
  • Hanya kombinasi dari huruf kapital (A-Z), huruf nomina (a-z), angka (0-9).
  • Special character !, $ , &, dll tidak dapat digunakan dalam penamaan variabel.
  • Bersifat case-sensitive sehingga penamaan variable algoritma, ALGORITMA, dan Algoritma adalah 3 variable yang berbeda
  • Tidak boleh menggunakan keywords pada Python

Python Data Types¶

Untuk mengecek tipe data python dapat menggunakan type()

(1) 🔠 String¶

Python mewakili string apapun sebagai object str. Adapun beberapa cara untuk membuat nilai string:

  • menggunakan '' (contoh: 'Jakarta' atau 'saya lahir di Bekasi')
  • mengunakan "" (contoh: "hari jum'at")
  • menggunakan ''' atau """ (contoh: '''Dia bersorak "Hore! Sudah hari Jum'at"''')
In [ ]:
dss_name
Out[ ]:
'Data Science Series: Streamlining Invoice Processing'
In [ ]:
# code here
type(dss_name)
Out[ ]:
str

(2) 🔢 Number¶

Untuk menyimpan number, python memiliki dua tipe data asli yang disebut int dan float.

  • int digunakan untuk menyimpan bilangan bulat (yaitu: 1,2,-3)
  • float digunakan untuk menyimpan bilangan real (yaitu: 0.7, -1.8, -1000.0) 👉🏻 bilangan desimal.
In [ ]:
# code here
nilai_ta = 100
nilai_fiqey = 90.0 
In [ ]:
nilai_ta - nilai_fiqey
Out[ ]:
10.0

✨ Operasi Matematika ✨

  • + - Penambahan
  • - - Pengurangan
  • * - Perkalian
  • / - Pembagian
  • // - Pembagian yang dibulatkan ke bawah
  • % - Modulus (sisa pembagian)
  • ** - Eksponen (pangkat)

(3) ✅ Boolean¶

Boolean hanya berisikan nilai True atau False. Biasanya untuk menunjukkan kebenaran suatu kondisi

In [ ]:
2 + 2 == 5
# == --> untuk mengecek kondisi (apakah sama?)
Out[ ]:
False

(4) 🗃️ List¶

list digunakan untuk menyimpan beberapa nilai dalam python. Untuk membuatnya, cukup letakan nilai di dalam tanda kurung siku []

In [ ]:
# list berikut mengandung informasi nama, kota domisili, umur, apakah sudah menikah?
data_list = ['Sari', 'Jakarta', 27, False]
In [ ]:
# cek tipe data my_list
type(data_list)
Out[ ]:
list

✨ Operasi List ✨

Dalam sebuah list kita dapat melakukan beberapa operasi:

  • list_x.append(a): Menambahkan a ke dalam list_x.
  • list_x.remove(a): Menghapus nilai a dari list_x.

Operasi Indexing:

  • list_x[i]: Mengakses elemen ke-i dari list_x.

📌 Additional Information

  • Python menggunakan sistem zero based indexing yang berarti, urutan pada python dimulai dari angka 0.
In [ ]:
# akses informasi kota domilisi dari seorang Sari, dimana informasi kota domilisi terletak di urutan ke-2 (index ke-1)
data_list[1]
Out[ ]:
'Jakarta'
In [ ]:
# coba kita tambahkan informasi pekerjaan 'Sari', yaitu 'Data Scientist'
data_list.append('Data Scientist')
data_list
Out[ ]:
['Sari', 'Jakarta', 27, False, 'Data Scientist']

(5) 🧾Dictionary¶

dictionary atau dict digunakan untuk menyimpan data dalam bentuk pasangan key-value. Dalam dictionary, setiap key harus unik, dan value-nya bisa berupa berbagai tipe data seperti string, integer, list, atau bahkan dictionary lainnya.

✨Dictionary didefinisikan dengan menggunakan tanda kurung kurawal {}.

In [ ]:
# dictionary berisi informasi tentang seseorang
data_dict = {
    'nama': 'Sari',
    'kota_domisili': 'Jakarta',
    'umur': 27,
    'menikah': False
}
In [ ]:
type(data_dict)
Out[ ]:
dict
In [ ]:
data_dict['kota_domisili']
Out[ ]:
'Jakarta'

Dictionary memiliki keunggulan daripada list karena memungkinkan penyimpanan data dengan key yang bersifat unik, sehingga memudahkan akses dan manipulasi data berdasarkan identifikasi key, bukan hanya index angka.

📌 Highlight Poin: Tipe Data di Python¶

  1. String : berisi satu karakter atau lebih, dengan karakter yang bisa berupa huruf, angka, simbol, atau karakter khusus.
  2. Number :
    • int untuk bilangan bulat
    • float untuk bilangan real.
  3. Boolean : hanya berisikan nilai True atau False.
  4. List : untuk menyimpan beberapa nilai dalam Python, dengan sintaks tanda kurung siku [].
  5. Dictionary : mirip seperti list, namun dictionary dalam bentuk pasangan key-value dan menggunakan syntax tanda kurung kurawal {}

Exploring Python Functions and Looping Concepts¶

Function¶

Function merupakan sekelompok perintah yang digunakan untuk melakukan tugas tertentu. Ketika kita melakukan sesuatu yang berulang dan rumit, alangkah baiknya kita menggunakan fungsi agar tidak ada langkah yang berubah maupun penulisan kode yang salah. Penulisan umum sebuah fungsi yaitu:

def nama_fungsi(parameter):
    perintah

Pada syntax umum di atas, def merupakan inisiator untuk sebuah fungsi. Sementara hal-hal yang harus kita tentukan yaitu nama dari fungsi, parameter yang akan digunakan di dalamnya, serta perintah atau kode.

Sebagai contoh, kita akan membuat sebuah fungsi luas segitiga:

In [ ]:
# fungsi luas_segitiga
def luas_persegi(sisi):
    hasil = sisi * sisi
    return hasil
In [ ]:
# memanggil fungsi
luas_persegi(sisi = 4)
Out[ ]:
16

Looping (Perulangan)¶

for loop digunakan untuk mengulangi/mengiterasi suatu urutan (dapat berupa list atau string).

Contoh for loop untuk mengiterasi elemen pada list:

In [ ]:
jabodetabek = ['Jakarta', 'Bogor', 'Depok', 'Tangerang', 'Bekasi']

# for loop
for x in jabodetabek:
  print(x)
Jakarta
Bogor
Depok
Tangerang
Bekasi

Working with Dataframe pandas¶

pandas adalah library yang powerful sebagai tools analisis data dan struktur pada Python. Library ini memiliki kemampuan sebagai berikut:

  • pandas mampu mengolah data menjadi mudah karena mempunyai objek bernama DataFrame.

  • pandas memiliki function yang mampu mengolah dataframe dengan menerapkan berbagai operasi dan teknik seperti join, agregasi, grouping, dan lain sebagainya

Lebih lengkapnya silahkan kunjungi official documentation

Untuk menggunakan pandas, kita perlu import terlebih dahulu library dengan cara berikut ini:

In [ ]:
import pandas as pd
print(pd.__version__)
2.1.4

Understanding Dataframe pandas¶

Read Data¶

Untuk membaca data atau file dengan format .csv dapat menggunakan method .read_csv().

Syntax:

pandas.read_csv("path/data")

🚀 Bacalah data receipts.csv yang berada dalam folder data_input

In [ ]:
receipts = pd.read_csv('data_input/receipt_cleaned.csv')
receipts
Out[ ]:
receipt_id item_name count price total_price
0 ID0000 real ganache 1.0 16500 45500
1 ID0000 egg tart 1.0 13000 45500
2 ID0000 pizza toast 1.0 16000 45500
3 ID0001 kopi susu kolonel 1.0 23000 23000
4 ID0002 s-ovaltine 1.0 20000 20000
5 ID0003 m-carame 1 black tea 1.0 28000 28000
6 ID0004 bbq chicken 1.0 41000 41000
7 ID0005 le mineral 1.0 8000 8000
8 ID0006 potato sausage bread 1.0 19000 123000
9 ID0006 oreo green tea spread 1.0 52000 123000
10 ID0006 white choco banana spread 1.0 52000 123000
11 ID0007 choco devil 4.0 63636 59500
12 ID0007 cp 360 club card 1.0 -9545 59500
13 ID0008 talam ungu 3.0 19500 11700
14 ID0008 mika kecil 1.0 0 11700
15 ID0009 tahu ikan oma giok 1.0 20000 20000
16 ID0010 serbu 2.0 40000 60000
17 ID0010 choco peanut bread 2.0 20000 60000
18 ID0011 se'i sapi sambal matah ( r ) 1.0 20000 89100
19 ID0011 se'i s-pi lada hitam (j) 1.0 35000 89100
20 ID0011 nasi putih 2.0 10000 89100
21 ID0011 milk shake coklat 1.0 16000 89100
22 ID0012 es kopi susu 4.0 72000 72000
23 ID0013 mineral 600 ml 1.0 7727 45500
24 ID0013 bulgogi rice r 1.0 33636 45500
25 ID0014 arem arem 2.0 24000 39600
26 ID0014 kroket 1.0 12000 39600
27 ID0015 arem arem 2.0 24000 59400
28 ID0015 pepenero pastel 2.0 30000 59400
29 ID0016 tt 1.0 20000 20000
30 ID0017 lemonade 16oz 1.0 20000 20000
31 ID0018 beef c roll 3pcs 1.0 10000 25000
32 ID0018 kaya bred 1.0 15000 25000
33 ID0019 futami 17 green tea (clas 1.0 12500 42500
34 ID0019 egg tart 1.0 13000 42500
35 ID0019 grain croque monsieur 1.0 17000 42500

Deskripsi Data:

Data receipt_cleaned.csv merupakan data transaksi atau struk pembelian dari suatu tempat seperti kafe atau restoran. Berikut adalah deskripsi untuk setiap kolom dalam data:

  • receipt_id: Nomor identifikasi unik untuk setiap struk pembelian.
  • item_name: Nama produk atau item yang dibeli.
  • count: Jumlah unit dari suatu produk yang dibeli.
  • price: Harga per unit dari suatu produk.
  • total_price: Total harga untuk suatu produk (jumlah unit dikalikan dengan harga per unit).

Lakukan pengamatan 5 data teratas dari receipts menggunakan .head()

In [ ]:
# code here
receipts.head()
Out[ ]:
receipt_id item_name count price total_price
0 ID0000 real ganache 1.0 16500 45500
1 ID0000 egg tart 1.0 13000 45500
2 ID0000 pizza toast 1.0 16000 45500
3 ID0001 kopi susu kolonel 1.0 23000 23000
4 ID0002 s-ovaltine 1.0 20000 20000

pandas Data Types¶

  • Dataframe merupakan tabel/data tabular dua dimensi yaitu baris dan kolom.
  • Dataframe terdiri dari beberapa Series (kolom).
  • Dalam satu series harus memiliki tipe data yang sama.
  • pandas akan menentukan tipe data dari masing-masing Series, tapi hasil dari pandas tidak selalu benar.

Berikut rangkuman tipe data pandas:

💡 Notes: Fokus pada kolom Pandas dtype dan Usage

Pandas dtype Python type Usage
object, str str Text or mixed numeric and non-numeric values
int64 int Integer numbers
float64 float Floating point numbers
bool bool True/False values
datetime64[ns] - Date and time values
timedelta[ns] - Differences between two datetimes
category - Finite list of text values

Referensi: Overview of Pandas Data Types

Tipe data Pandas:

  • int64: Integer (bilangan bulat, tanpa koma)
  • float64: Bilangan desimal (berkoma)
  • object: Text (string)
  • category: Kategorikal
  • datetime64[ns]: Data waktu

Karakteristik tipe data category :

  • Dapat dikelompokkan menjadi beberapa kelompok (category)
  • Nilainya berulang

Saat kita membaca data dengan pd.read_csv(), pandas akan mencoba menentukan tipe data dari setiap kolom. Lakukan investigasi awal untuk melihat struktur data terhadap object DataFrame dengan menggunakan method .info().

In [ ]:
# cek tipe data
receipts.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 36 entries, 0 to 35
Data columns (total 5 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   receipt_id   36 non-null     object 
 1   item_name    36 non-null     object 
 2   count        36 non-null     float64
 3   price        36 non-null     int64  
 4   total_price  36 non-null     int64  
dtypes: float64(1), int64(2), object(2)
memory usage: 1.5+ KB

💡 NOTES

Dengan menggunakan method .info(), kita dapat memeriksa informasi lengkap dari DataFrame kita:

  • Dimensi data: jumlah baris dan kolom (.shape)
  • Nama kolom (.columns)
  • Tipe data setiap kolom (.dtypes)
  • Penggunaan memori

Kolom manakah yang memiliki format tipe data yang belum sesuai?

Jawaban: item_name -> category

Untuk mengubah tipe data pada pandas, dapat menggunakan method astype().

Syntax

df['column_name'] = df['column_name'].astype('new_data_types')
In [ ]:
# check data type
receipts['item_name'] = receipts['item_name'].astype('category')

receipts.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 36 entries, 0 to 35
Data columns (total 5 columns):
 #   Column       Non-Null Count  Dtype   
---  ------       --------------  -----   
 0   receipt_id   36 non-null     category
 1   item_name    36 non-null     category
 2   count        36 non-null     float64 
 3   price        36 non-null     int64   
 4   total_price  36 non-null     int64   
dtypes: category(2), float64(1), int64(2)
memory usage: 3.1 KB

Additional: Object & Categorical Variables¶

Karakteristik tipe data object:

  • Mirip seperti tipe data string pada python
  • Kemunculannya unik (artinya setiap row kemunculannya hanya 1 atau beberapa) Contoh: ID, NIK, No.HP

Karakteristik tipe data category:

  • Dapat dikelompokkan menjadi beberapa kelompok (category)
  • Kemunculannya berulang Contoh: Gender, Nama Barang dalam data transaksi, provinsi, jenis kartu kredit, kategori barang: elektronik dan non elektronik

Dua alasan mengapa kita perlu menggunakan tipe data categorical:

  1. Business Side: memudahkan Analyst untuk memilih metode statistik atau tipe plot mana yang digunakan untuk mengolah data.
  2. Technical Side: menghemat memori dan menambah kecepatan komputasional

💡 Kita bisa menggunakan method berikut untuk mengidentifikasi kolom mana yang cocok untuk disimpan ke tipe data category

  • .unique() untuk melihat nilai unik di setiap Series/kolom
  • .nunique() untuk melihat jumlah nilai unik/distict pada Series/Dataframe

Berikut contoh syntax untuk mengecek nilai unik pada sebuah Series

df['nama_kolom'].unique()

Master Data Manipulation & Pre-processing¶

Dalam analisis data, manipulasi dan wrangling data adalah langkah awal kritis yang menentukan keberhasilan analisis. Manipulasi data memungkinkan pengaturan dan filtering data menjadi format yang siap analisis, sementara wrangling data memastikan kebersihan dan keakuratan data. Kedua proses ini fundamental dalam mengubah data mentah menjadi wawasan yang berharga dan dapat diandalkan.

Dive Deeper into String Data Types¶

✨ Dalam proses pembersihan data, terutama pada kolom dengan tipe data string yang berasal dari object dalam DataFrame Pandas, operasi seperti lower(), upper(), dan replace() sering digunakan untuk menyederhanakan atau menstandarisasi teks.

🔻 .str.lower(): digunakan untuk mengubah semua karakter menjadi huruf kecil

In [ ]:
# mengubah semua teks dalam kolom 'item_name' menjadi huruf kecil
receipts['item_name'].str.lower()[5]
Out[ ]:
'm-carame 1 black tea'

🔻 upper(): digunakan untuk mengubah semua karakter menjadi huruf kapital

In [ ]:
# mengubah semua teks dalam kolom 'receipt_id' menjadi huruf besar
receipts['receipt_id'].str.upper()[5]
Out[ ]:
'ID0003'

🔻 replace(): digunakan untuk mengganti karakter tertentu dari sebuah string

In [ ]:
# mengganti '-' dengan ' ' (spasi) dalam kolom 'item_name'
receipts['item_name'].str.replace('-', ' ')[5]
Out[ ]:
'm carame 1 black tea'

Contingency Tables / Frequency Tables¶

Untuk menghitung contigency tables, kita dapat menggunakan method .value_counts(). Kegunaan: Menghitung banyak baris pada setiap category dalam 1 kolom, dan defaultnya diurutkan secara descending

Parameter:

  • ascending=True: urutkan nilai dalam urutan menaik

Mari kita melihat jumlah pengiriman setiap bulannya

In [ ]:
receipts['receipt_id'].value_counts()
Out[ ]:
receipt_id
ID0011    4
ID0000    3
ID0006    3
ID0019    3
ID0007    2
ID0008    2
ID0013    2
ID0014    2
ID0015    2
ID0018    2
ID0010    2
ID0004    1
ID0005    1
ID0003    1
ID0009    1
ID0001    1
ID0002    1
ID0012    1
ID0016    1
ID0017    1
Name: count, dtype: int64

❓ Mari kita lihat barang apa yang sering dibeli?

In [ ]:
# code here
receipts['item_name'].value_counts(ascending = False)
Out[ ]:
item_name
arem arem                       2
egg tart                        2
real ganache                    1
se'i s-pi lada hitam (j)        1
nasi putih                      1
milk shake coklat               1
es kopi susu                    1
mineral 600 ml                  1
bulgogi rice r                  1
kroket                          1
pepenero pastel                 1
tt                              1
lemonade 16oz                   1
beef c roll 3pcs                1
kaya bred                       1
futami 17 green tea (clas       1
se'i sapi sambal matah ( r )    1
choco peanut bread              1
serbu                           1
tahu ikan oma giok              1
mika kecil                      1
talam ungu                      1
cp 360 club card                1
choco devil                     1
white choco banana spread       1
oreo green tea spread           1
potato sausage bread            1
le mineral                      1
bbq chicken                     1
m-carame 1 black tea            1
s-ovaltine                      1
kopi susu kolonel               1
pizza toast                     1
grain croque monsieur           1
Name: count, dtype: int64

Perform Conditional Subsetting Operations¶

Conditional subsetting bermaksud untuk mengambil sebagian data dari DataFrame berdasarkan suatu kondisi tertentu, seperti:

  • Pengiriman yang terjadi pada bulan Januari
  • Pengiriman dengan jarak lebih dari 10km
  • Pengiriman yang memiliki biaya add_cost, atau add_cost != 0

Syntax penulisan untuk conditional subsetting adalah:

df[df['column_name'] <comparison_operator> <value>]

atau

df[df.column_name <comparison_operator> <value>]

Contoh comparison_operator adalah seperti ==, !=, >, >=, <, <=.

Menampilkan data yang terjadi pada hari Senin

receipts[receipts['item_name'] == 'arem arem']

kondisi -> receipts['item_name'] == 'arem arem'

In [ ]:
receipts[receipts['item_name'] == 'arem arem']
Out[ ]:
receipt_id item_name count price total_price
25 ID0014 arem arem 2.0 24000 39600
27 ID0015 arem arem 2.0 24000 59400

❓ Menampilkan data pembelian yang memiliki receipt_id ID0015

In [ ]:
# code here
receipts[receipts['receipt_id'] == 'ID0015']
Out[ ]:
receipt_id item_name count price total_price
27 ID0015 arem arem 2.0 24000 59400
28 ID0015 pepenero pastel 2.0 30000 59400

❓ Menampilkan data pembelian yang memiliki total pengeluaran lebih dari > Rp 50.000

In [ ]:
# code here
receipt_above50k = receipts[receipts['total_price'] > 50000]
receipt_above50k
Out[ ]:
receipt_id item_name count price total_price
8 ID0006 potato sausage bread 1.0 19000 123000
9 ID0006 oreo green tea spread 1.0 52000 123000
10 ID0006 white choco banana spread 1.0 52000 123000
11 ID0007 choco devil 4.0 63636 59500
12 ID0007 cp 360 club card 1.0 -9545 59500
16 ID0010 serbu 2.0 40000 60000
17 ID0010 choco peanut bread 2.0 20000 60000
18 ID0011 se'i sapi sambal matah ( r ) 1.0 20000 89100
19 ID0011 se'i s-pi lada hitam (j) 1.0 35000 89100
20 ID0011 nasi putih 2.0 10000 89100
21 ID0011 milk shake coklat 1.0 16000 89100
22 ID0012 es kopi susu 4.0 72000 72000
27 ID0015 arem arem 2.0 24000 59400
28 ID0015 pepenero pastel 2.0 30000 59400
In [ ]:
# optional: mengambil yang unique saja
receipt_above50k['receipt_id'].unique()
Out[ ]:
['ID0006', 'ID0007', 'ID0010', 'ID0011', 'ID0012', 'ID0015']
Categories (20, object): ['ID0000', 'ID0001', 'ID0002', 'ID0003', ..., 'ID0016', 'ID0017', 'ID0018', 'ID0019']

Document Understanding Transformer (DUT) Utilization¶

Dalam bisnis, kebutuhan untuk mengubah gambar menjadi teks sering muncul, yang awalnya diatasi dengan teknologi OCR (Optical Character Recognition).

Namun, OCR memiliki beberapa kelemahan, yaitu:

  1. Memerlukan langkah-langkah preprocessing yang cukup dalam sebelum diterapkan pada OCR

No description has been provided for this image

  1. Beberapa sistem OCR terbatas dalam mengenali berbagai bahasa atau karakter khusus.

Seiring berkembangnya riset, muncul model OCR-free Document Understanding Transformer (Donut) yang mengintegrasikan arsitektur Transformer tanpa bergantung pada OCR, memungkinkan ekstraksi teks dari gambar dengan lebih akurat dan tanpa batasan bahasa.

Introduction to 🍩 Donut Model¶

What is 🍩 Donut Model?¶

Donut adalah model berbasis Transformer yang dirancang untuk tugas pemahaman dokumen yang tidak menggunakan OCR (Optical Character Recognition), menggunakan teknik mengekstrak informasi bermakna dari gambar dokumen yang tidak terstruktur.

Proses mengubah dokumen gambar menjadi output text/JSON yang dilakukan oleh model Donut melalui beberapa langkah:

No description has been provided for this image

💡Dalam model Donut, Swin Transformer digunakan untuk ekstraksi fitur dari gambar dokumen secara efektif. Swin Transformer adalah pengembangan Transformer untuk kasus visual/gambar yang mampu menandingi CNN. Kemampuannya untuk memproses informasi secara hierarkis dan memahami konteks global memungkinkan model Donut mengenali dan memahami teks dan layout dokumen dengan lebih akurat, tanpa bergantung pada OCR.

Ini meningkatkan efisiensi dan akurasi dalam pengolahan dokumen, terutama untuk dokumen dengan layout kompleks.

🤖 Introduction to Transformer Deep Learning NLP: Backbone of GPT

Transformer adalah sebuah arsitektur neural network yang diperkenalkan pada tahun 2017 dalam makalah berjudul "Attention Is All You Need" oleh Ashish Vaswani dan tim peneliti dari Google AI.

Arsitektur Transformer adalah terobosan besar dalam bidang natural language processing (NLP) dan telah mengubah cara kita mendekati tugas-tugas seperti machine translation, NLP, dan banyak aplikasi lain yang melibatkan pemrosesan sequential data. Contoh model Transformer seperti BERT, GPT (Generative Pre-trained Transformer), dan T5 (Text-to-Text Transfer Transformer)

✨ State-of-The-Art Transformer: ✨

  • Mampu memahami konteks dari data sequential (misal mengetahui makna dari suatu kalimat) dengan konsep "Attention"
  • Mampu bekerja secara pararel.
  • Memperhatikan urutan dari data menggunakan Positional Encoding.
  • Skalabilitas tinggi
  • Dapat digunakan dalam berbagai konteks, termasuk pemrosesan gambar, suara, dan natural language.

Why Use 🍩 Donut Model?¶

Model Donut lebih efektif daripada OCR tradisional dalam mengenali teks dari gambar dokumen, terutama yang memiliki layout kompleks atau kualitas gambar yang rendah. Donut dapat memahami konteks dan layout dokumen secara lebih akurat dan cepat.

Selain document information extraction, Donut Model dapat melakukan berbagai tugas pemrosesan dokumen lainnya, termasuk:

  • Klasifikasi Dokumen: Mengkategorikan dokumen ke dalam berbagai jenis atau kelas berdasarkan isi dan strukturnya.
  • QnA: Menganalisis dan menjawab pertanyaan yang berkaitan dengan konten dalam dokumen.

💡 Tak hanya itu, fakta menarik dari model Donut adalah bahwa model ini dilatih menggunakan teks dalam berbagai bahasa dengan memanfaatkan SynthDoG. SynthDoG adalah sebuah generator dokumen sintetis yang membantu pelatihan model Donut untuk fleksibel dalam berbagai bahasa dan domain.

No description has been provided for this image

Dengan menggunakan SynthDoG, Donut tidak hanya efektif dalam memahami dokumen dalam bahasa Inggris, tetapi juga dalam bahasa lain, memberikan kemampuan pemahaman dokumen yang lebih luas dan serbaguna.

How to Use 🍩 Donut Model for Document Information Extraction?¶

Model Donut bisa digunakan dalam Python dengan syarat kita telah melakukan instalasi yang diperlukan dan melakukan impor library yang diperlukan.

In [ ]:
import re
import os
import torch

from PIL import Image
from transformers import DonutProcessor, VisionEncoderDecoderModel
c:\Users\user\miniconda3\envs\dss_donut\lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

💡 Notes:

  • DonutProcessor adalah kelas yang digunakan untuk memproses input untuk model Donut.
  • VisionEncoderDecoderModel adalah kelas yang mengimplementasikan arsitektur model Vision Encoder-Decoder, yang dapat digunakan untuk tugas pengolahan citra dan natural language processing.

🔻Mari kita load model donut yang akan kita gunakan

⚠️ It will take longer if it's your first time loading it.

In [ ]:
processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
Could not find image processor class in the image processor config or the model config. Loading based on pattern matching with the model's feature extractor configuration.
c:\Users\user\miniconda3\envs\dss_donut\lib\site-packages\torch\_utils.py:831: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly.  To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()
  return self.fget.__get__(instance, owner)()
In [ ]:
# set model device
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
Out[ ]:
VisionEncoderDecoderModel(
  (encoder): DonutSwinModel(
    (embeddings): DonutSwinEmbeddings(
      (patch_embeddings): DonutSwinPatchEmbeddings(
        (projection): Conv2d(3, 128, kernel_size=(4, 4), stride=(4, 4))
      )
      (norm): LayerNorm((128,), eps=1e-05, elementwise_affine=True)
      (dropout): Dropout(p=0.0, inplace=False)
    )
    (encoder): DonutSwinEncoder(
      (layers): ModuleList(
        (0): DonutSwinStage(
          (blocks): ModuleList(
            (0-1): 2 x DonutSwinLayer(
              (layernorm_before): LayerNorm((128,), eps=1e-05, elementwise_affine=True)
              (attention): DonutSwinAttention(
                (self): DonutSwinSelfAttention(
                  (query): Linear(in_features=128, out_features=128, bias=True)
                  (key): Linear(in_features=128, out_features=128, bias=True)
                  (value): Linear(in_features=128, out_features=128, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
                (output): DonutSwinSelfOutput(
                  (dense): Linear(in_features=128, out_features=128, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
              )
              (drop_path): DonutSwinDropPath(p=0.1)
              (layernorm_after): LayerNorm((128,), eps=1e-05, elementwise_affine=True)
              (intermediate): DonutSwinIntermediate(
                (dense): Linear(in_features=128, out_features=512, bias=True)
                (intermediate_act_fn): GELUActivation()
              )
              (output): DonutSwinOutput(
                (dense): Linear(in_features=512, out_features=128, bias=True)
                (dropout): Dropout(p=0.0, inplace=False)
              )
            )
          )
          (downsample): DonutSwinPatchMerging(
            (reduction): Linear(in_features=512, out_features=256, bias=False)
            (norm): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
          )
        )
        (1): DonutSwinStage(
          (blocks): ModuleList(
            (0-1): 2 x DonutSwinLayer(
              (layernorm_before): LayerNorm((256,), eps=1e-05, elementwise_affine=True)
              (attention): DonutSwinAttention(
                (self): DonutSwinSelfAttention(
                  (query): Linear(in_features=256, out_features=256, bias=True)
                  (key): Linear(in_features=256, out_features=256, bias=True)
                  (value): Linear(in_features=256, out_features=256, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
                (output): DonutSwinSelfOutput(
                  (dense): Linear(in_features=256, out_features=256, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
              )
              (drop_path): DonutSwinDropPath(p=0.1)
              (layernorm_after): LayerNorm((256,), eps=1e-05, elementwise_affine=True)
              (intermediate): DonutSwinIntermediate(
                (dense): Linear(in_features=256, out_features=1024, bias=True)
                (intermediate_act_fn): GELUActivation()
              )
              (output): DonutSwinOutput(
                (dense): Linear(in_features=1024, out_features=256, bias=True)
                (dropout): Dropout(p=0.0, inplace=False)
              )
            )
          )
          (downsample): DonutSwinPatchMerging(
            (reduction): Linear(in_features=1024, out_features=512, bias=False)
            (norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
          )
        )
        (2): DonutSwinStage(
          (blocks): ModuleList(
            (0-13): 14 x DonutSwinLayer(
              (layernorm_before): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
              (attention): DonutSwinAttention(
                (self): DonutSwinSelfAttention(
                  (query): Linear(in_features=512, out_features=512, bias=True)
                  (key): Linear(in_features=512, out_features=512, bias=True)
                  (value): Linear(in_features=512, out_features=512, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
                (output): DonutSwinSelfOutput(
                  (dense): Linear(in_features=512, out_features=512, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
              )
              (drop_path): DonutSwinDropPath(p=0.1)
              (layernorm_after): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
              (intermediate): DonutSwinIntermediate(
                (dense): Linear(in_features=512, out_features=2048, bias=True)
                (intermediate_act_fn): GELUActivation()
              )
              (output): DonutSwinOutput(
                (dense): Linear(in_features=2048, out_features=512, bias=True)
                (dropout): Dropout(p=0.0, inplace=False)
              )
            )
          )
          (downsample): DonutSwinPatchMerging(
            (reduction): Linear(in_features=2048, out_features=1024, bias=False)
            (norm): LayerNorm((2048,), eps=1e-05, elementwise_affine=True)
          )
        )
        (3): DonutSwinStage(
          (blocks): ModuleList(
            (0-1): 2 x DonutSwinLayer(
              (layernorm_before): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
              (attention): DonutSwinAttention(
                (self): DonutSwinSelfAttention(
                  (query): Linear(in_features=1024, out_features=1024, bias=True)
                  (key): Linear(in_features=1024, out_features=1024, bias=True)
                  (value): Linear(in_features=1024, out_features=1024, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
                (output): DonutSwinSelfOutput(
                  (dense): Linear(in_features=1024, out_features=1024, bias=True)
                  (dropout): Dropout(p=0.0, inplace=False)
                )
              )
              (drop_path): DonutSwinDropPath(p=0.1)
              (layernorm_after): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
              (intermediate): DonutSwinIntermediate(
                (dense): Linear(in_features=1024, out_features=4096, bias=True)
                (intermediate_act_fn): GELUActivation()
              )
              (output): DonutSwinOutput(
                (dense): Linear(in_features=4096, out_features=1024, bias=True)
                (dropout): Dropout(p=0.0, inplace=False)
              )
            )
          )
        )
      )
    )
    (pooler): AdaptiveAvgPool1d(output_size=1)
  )
  (decoder): MBartForCausalLM(
    (model): MBartDecoderWrapper(
      (decoder): MBartDecoder(
        (embed_tokens): Embedding(57580, 1024, padding_idx=1)
        (embed_positions): MBartLearnedPositionalEmbedding(770, 1024)
        (layers): ModuleList(
          (0-3): 4 x MBartDecoderLayer(
            (self_attn): MBartAttention(
              (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
              (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
              (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
              (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
            )
            (activation_fn): GELUActivation()
            (self_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
            (encoder_attn): MBartAttention(
              (k_proj): Linear(in_features=1024, out_features=1024, bias=True)
              (v_proj): Linear(in_features=1024, out_features=1024, bias=True)
              (q_proj): Linear(in_features=1024, out_features=1024, bias=True)
              (out_proj): Linear(in_features=1024, out_features=1024, bias=True)
            )
            (encoder_attn_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
            (fc1): Linear(in_features=1024, out_features=4096, bias=True)
            (fc2): Linear(in_features=4096, out_features=1024, bias=True)
            (final_layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
          )
        )
        (layernorm_embedding): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
        (layer_norm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
      )
    )
    (lm_head): Linear(in_features=1024, out_features=57580, bias=False)
  )
)

Document Information Extraction Using Donut¶

No description has been provided for this image

Tentu, untuk melakukan ekstraksi informasi dari dokumen, langkah pertama yang perlu dilakukan adalah menyiapkan gambar-gambar dokumen yang akan dibaca. Oleh karena itu, kita akan mencoba membaca beberapa file invoice yang terdapat pada folder data_input.

1️⃣ Read Document Images¶

  1. Persiapkan dimana letak peletakan file-file gambar dokumennya.
In [ ]:
# folder path
dir_path = 'data_input/valid/'
  1. Mari membuat dua list,
    • img_filenames untuk menyimpan nama file dengan ekstensi .png,
    • labels untuk menyimpan nama file dengan ekstensi .txt

Hasilnya, dua list tersebut berisi nama file gambar dan label teks dari dokumen gambar yang kita miliki.

In [ ]:
img_filenames = [] # list nama file gambar
images = [] # list objek gambar
labels = [] # list label dari gambar

# iterasi direktori
for file in os.listdir(dir_path):
    if file.endswith(('.png', '.jpg', '.jpeg')):
        # menyimpan nama invoice ke dalam list
        img_filenames.append(file)
        
        # membuka dan menyimpan objek gambar ke dalam list
        images.append(Image.open(dir_path + file))


    elif file.endswith('.txt'):
        # membuka file teks dan membaca isinya, lalu menyimpan ke dalam list
        with open(dir_path + file, 'r') as label_file: 
            labels.append(label_file.read())
In [ ]:
# mencetak list nama file gambar
print(img_filenames)
['ID0000.png', 'ID0001.png', 'ID0002.png', 'ID0003.png', 'ID0004.png', 'ID0005.png', 'ID0006.png', 'ID0007.png', 'ID0008.png', 'ID0009.png', 'ID0010.png', 'ID0011.png', 'ID0012.png', 'ID0013.png', 'ID0014.png', 'ID0015.png', 'ID0016.png', 'ID0017.png', 'ID0018.png', 'ID0019.png', 'ID0020.png', 'ID0021.png', 'ID0022.png', 'ID0023.png', 'ID0024.png', 'ID0025.png', 'ID0026.png', 'ID0027.png', 'ID0028.png', 'ID0029.png', 'ID0030.png', 'ID0031.png', 'ID0032.png', 'ID0033.png', 'ID0034.png', 'ID0035.png', 'ID0036.png', 'ID0037.png', 'ID0038.png', 'ID0039.png', 'ID0040.png', 'ID0041.png', 'ID0042.png', 'ID0043.png', 'ID0044.png', 'ID0045.png', 'ID0046.png', 'ID0047.png', 'ID0048.png', 'ID0049.png', 'ID0050.png', 'ID0051.png', 'ID0052.png', 'ID0053.png', 'ID0054.png', 'ID0055.png', 'ID0056.png', 'ID0057.png', 'ID0058.png', 'ID0059.png', 'ID0060.png', 'ID0061.png', 'ID0062.png', 'ID0063.png', 'ID0064.png', 'ID0065.png', 'ID0066.png', 'ID0067.png', 'ID0068.png', 'ID0069.png', 'ID0070.png', 'ID0071.png', 'ID0072.png', 'ID0073.png', 'ID0074.png', 'ID0075.png', 'ID0076.png', 'ID0077.png', 'ID0078.png', 'ID0079.png', 'ID0080.png', 'ID0081.png', 'ID0082.png', 'ID0083.png', 'ID0084.png', 'ID0085.png', 'ID0086.png', 'ID0087.png', 'ID0088.png', 'ID0089.png', 'ID0090.png', 'ID0091.png', 'ID0092.png', 'ID0093.png', 'ID0094.png', 'ID0095.png', 'ID0096.png', 'ID0097.png', 'ID0098.png', 'ID0099.png']

🔻 Mari coba kita lihat label dan gambar ke-7

In [ ]:
# code here
print(labels[6])
images[6]
{'menu': [{'nm': 'POTATO SAUSAGE BREAD', 'cnt': '1', 'price': '19,000'}, {'nm': 'OREO GREEN TEA SPREAD', 'cnt': '1', 'price': '52,000'}, {'nm': 'WHITE CHOCO BANANA SPREAD', 'cnt': '1', 'price': '52,000'}], 'total': {'total_price': '123,000', 'creditcardprice': '123,000'}}
Out[ ]:
No description has been provided for this image

2️⃣ Document Pre-processing¶

Sebagaimana umumnya pada model deep learning, diperlukan penyesuaian bentuk input sebelum dapat digunakan untuk prediksi atau pemrosesan lebih lanjut. Dalam konteks Donut Model, tahap ini melibatkan konversi gambar ke dalam format tensor dengan cara yang sederhana menggunakan alat yang disebut Donut Processor.

🎯 Proses ini diperlukan karena model, terutama yang menggunakan framework seperti PyTorch, umumnya membutuhkan input dalam bentuk tensor.

In [ ]:
images[6].size
Out[ ]:
(864, 1296)
In [ ]:
pixel_values = processor(images[6], return_tensors="pt").pixel_values
print(pixel_values.shape)
torch.Size([1, 3, 1280, 960])

✨ Keterangan:

Makna ukuran pada tensor tersebut adalah [jumlah batch, jumlah channel (RGB), tinggi, lebar]. Dalam konteks ini, semua gambar diproses menjadi ukuran yang sama sebelum masuk ke model. Hal ini dilakukan karena model deep learning seringkali mensyaratkan agar seluruh input memiliki dimensi yang konsisten.

3️⃣ Sequence Generation¶

Selanjutnya, kita membiarkan model Donut secara otoregresif menghasilkan data terstruktur, mengggunakan metode .generate().

In [ ]:
task_prompt = "<s_cord-v2>"

✨ Secara umum, model Donut dapat digunakan untuk berbagai tugas, seperti:

  • Pemrosesan/Ekstraksi Dokumen (<s_cord-v2)
  • Klasifikasi Dokumen
  • Pertanyaan dan Jawaban Visual (VQA) pada Dokumen

No description has been provided for this image

Oleh karena itu, ketika ingin menggunakan model untuk salah satu dari tugas-tugas tersebut, kita perlu menentukan task_prompt agar mengarahkan model Donut untuk fokus pada tugas spesifik yang diinginkan, sehingga output yang dihasilkan dapat lebih relevan dan bermakna sesuai dengan kebutuhan pengguna.

Selengkapnya dapat di cek di Official Github Donut

In [ ]:
processor.tokenizer.pad_token_id
Out[ ]:
1
In [ ]:
outputs = model.generate(
        inputs=pixel_values.to(device),
        max_length=model.decoder.config.max_position_embeddings,
        decoder_input_ids=processor.tokenizer(task_prompt, add_special_tokens=False, return_tensors="pt")["input_ids"].to(device),
        pad_token_id=processor.tokenizer.pad_token_id,
        eos_token_id=processor.tokenizer.eos_token_id,
        bad_words_ids=[[processor.tokenizer.unk_token_id]],
        return_dict_in_generate=True,
        use_cache=True,
        
        # modify parameters
        early_stopping=True,
        num_beams=2,
        output_scores=True,
    )

✨ Fungsi model.generate() pada model Donut memungkinkan untuk menghasilkan sequence text dari model. Parameter utama-utama adalah berikut

  • inputs : Nilai piksel dari gambar yang digunakan sebagai input untuk di-generate from doc to text.
  • max_length: Panjang maksimum dari urutan yang dihasilkan.
  • decoder_input_ids: ID input untuk dekoder. Di sini, tokenizer memproses task_prompt (prompt tugas yang ditentukan) dan menghasilkan ID yang relevan. add_special_tokens=False Menunjukkan bahwa token khusus tidak ditambahkan dalam proses tokenisasi.
  • pad_token_id: ID dari token padding.
  • eos_token_id: ID dari token akhir urutan.
  • bad_words_ids: Daftar ID kata yang harus dihindari oleh model saat melakukan generasi.
  • return_dict_in_generate: Mengembalikan output dalam format dictionary yang lebih mudah diakses.
  • use_cache: Menentukan apakah hasil perhitungan sebelumnya harus disimpan untuk digunakan kembali, untuk meningkatkan efisiensi.

Parameter tambahannya adalah:

  • early_stopping: Jika diatur True, model akan berhenti menghasilkan output lebih lanjut jika syarat tertentu terpenuhi, seperti mencapai EOS token.
  • num_beams: Jumlah beams yang digunakan dalam pencarian beam.
  • output_scores: Apakah mengembalikan skor skor untuk setiap token yang dihasilkan bersamaan dengan teks.

📌 Untuk informasi lebih rinci, silahkan merujuk ke dokumentasi 🍩 Donut di sini.

4️⃣ Document Post-Processing: Sequence Token Cleaning¶

🔻 Output hasil generate dokumen menggunakan model Donut adalah tensor yang berisi sequence token-token yang perlu dilakukan decode sehingga kita dapat mengetahui isinya. Setiap angka dalam tensor ini merepresentasikan suatu token dalam model.

In [ ]:
outputs.sequences
Out[ ]:
tensor([[57579, 57526, 57528, 40769, 44398, 54165, 13663, 56992, 50990, 42990,
         43547,  5909, 57527, 57530,  1314, 57529, 57532, 17888, 56239, 57531,
         57522, 57528, 37040, 49429, 42990, 51290,  4004, 42990,  4008,  5176,
         40099, 39415, 57527, 57530,  1314, 57529, 57532, 20017, 52066, 57531,
         57522, 57528, 46193, 53278, 33938, 36277, 18081, 56701, 42990, 40266,
          5178,  5176, 40099, 39415, 57527, 57530,  1314, 57529, 57532, 20017,
         52066, 57531, 57525, 57544, 57546, 40474, 47108, 57545, 57564, 40474,
         47108, 57563, 57543,     2]])

🔻Maka dari itu, mari kita lakukan decode menggunakan processor yang sebelumnya juga digunakan untuk mengencode dengan menggunakan perintah processor.batch_decode()

In [ ]:
raw_sequence = processor.batch_decode(outputs.sequences)[0]
print(raw_sequence)
<s_cord-v2><s_menu><s_nm> POTATO SAUSAGE BREAD</s_nm><s_cnt> 1</s_cnt><s_price> 19,000</s_price><sep/><s_nm> OREO GREEN TEA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price><sep/><s_nm> WHITE CHOCO BANANA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price></s_menu><s_total><s_total_price> 123,000</s_total_price><s_creditcardprice> 123,000</s_creditcardprice></s_total></s>

✨ Ini adalah teks terstruktur yang telah didekode dari tensor. Teks ini mencakup informasi terkait pesanan menu, jumlah, harga, dan total pembayaran. Strukturnya mencakup tag-tag XML (<s_cord-v2>, <s_menu>, <s_nm>, <s_cnt>, <s_price>, <sep/>, <s_total>, <s_total_price>, <s_creditcardprice>) yang memberikan konteks terhadap data yang terkandung.

🔻 Selanjutnya, dilakukan pembersihan terhadap teks. Tahap ini mencakup penghapusan token EOS (</s>), token PAD (<pad>), dan tag pertama <s_cord-v2> yang menandakan awal dari tugas pertama

In [ ]:
sequence = raw_sequence.replace(processor.tokenizer.eos_token, "").replace(processor.tokenizer.pad_token, "") # remove </s> eos and <pad> token
sequence = re.sub(r"<.*?>", "", sequence, count=1).strip()  # remove <s_cord-v2> first task start token
sequence
Out[ ]:
'<s_menu><s_nm> POTATO SAUSAGE BREAD</s_nm><s_cnt> 1</s_cnt><s_price> 19,000</s_price><sep/><s_nm> OREO GREEN TEA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price><sep/><s_nm> WHITE CHOCO BANANA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price></s_menu><s_total><s_total_price> 123,000</s_total_price><s_creditcardprice> 123,000</s_creditcardprice></s_total>'

5️⃣ Output Conversion: Token to JSON¶

✨ Output dari model Donut yang dihasilkan dapat kita ubah menjadi format JSON (JavaScript Object Notation), di Python mirip dengan dictionary, untuk memudahkan pengolahan dan analisis lebih lanjut. Format JSON/ dictionary memungkinkan representasi data yang terstruktur dan mudah diakses.

In [ ]:
processor.token2json(sequence)
Out[ ]:
{'menu': [{'nm': 'POTATO SAUSAGE BREAD', 'cnt': '1', 'price': '19,000'},
  {'nm': 'OREO GREEN TEA SPREAD', 'cnt': '1', 'price': '52,000'},
  {'nm': 'WHITE CHOCO BANANA SPREAD', 'cnt': '1', 'price': '52,000'}],
 'total': {'total_price': '123,000', 'creditcardprice': '123,000'}}
In [ ]:
# gambar yang di generate (images[6])
images[6]
Out[ ]:
No description has been provided for this image

Di sini, kita akan mencapai hasil akhir dari proses generasi informasi dokumen menggunakan model Donut.

🍩 All-in-One Function to Generate Document Information¶

💡 Untuk memudahkan dan mengotomatisasi proses ini, khususnya ketika bekerja dengan banyak gambar, kita akan menggabungkan seluruh langkah, dari 1 hingga 5, ke dalam satu fungsi. Dengan demikian, kita dapat dengan mudah menerapkan fungsi ini pada banyak gambar secara berurutan melalui looping.

In [ ]:
def doc_to_text(input_img, task_prompt=task_prompt, model=model, processor=processor):
    # set model device
    device = "cuda" if torch.cuda.is_available() else "cpu"
    model.to(device)
    
    # document preprocessing
    pixel_values = processor(input_img, return_tensors="pt").pixel_values
    decoder_input_ids = processor.tokenizer(task_prompt, add_special_tokens=False, return_tensors="pt")["input_ids"]

    # sequence generation
    outputs = model.generate(
        pixel_values.to(device),
        decoder_input_ids=decoder_input_ids.to(device),
        max_length=model.decoder.config.max_position_embeddings,
        pad_token_id=processor.tokenizer.pad_token_id,
        eos_token_id=processor.tokenizer.eos_token_id,
        use_cache=True,
        bad_words_ids=[[processor.tokenizer.unk_token_id]],
        return_dict_in_generate=True,
        
        # modify parameters
        early_stopping=True,
        num_beams=2,
        output_scores=True,
    )
    
    # document post-processing: sequence token cleaning
    sequence = processor.batch_decode(outputs.sequences)[0]
    sequence = sequence.replace(processor.tokenizer.eos_token, "").replace(processor.tokenizer.pad_token, "")
    sequence = re.sub(r"<.*?>", "", sequence, count=1).strip()  # remove first task start token
    print(sequence)

    # output conversion: token to json
    output = processor.token2json(sequence)

    return output

🔻 Mari kita coba men-generate 20 gambar receipt/invoice yang kita miliki!

In [ ]:
# list untuk menyimpan hasil generate docs to text
preds = []

for img in images[:20]:
    out = doc_to_text(img)
    preds.append(out)
<s_menu><s_nm> REAL GANACHE</s_nm><s_cnt> 1</s_cnt><s_price> 16,500</s_price><sep/><s_nm> EGG TART</s_nm><s_cnt> 1</s_cnt><s_price> 13,000</s_price><sep/><s_nm> PIZZA TOAST</s_nm><s_cnt> 1</s_cnt><s_price> 16,000</s_price></s_menu><s_total><s_total_price> 45,500</s_total_price><s_cashprice> 50,000</s_cashprice><s_changeprice> 4,500</s_changeprice></s_total>
<s_menu><s_nm> Kopi Susu Kolonel</s_nm><s_cnt> 1</s_cnt><s_price> 23.000</s_price></s_menu><s_total><s_total_price> 23.000</s_total_price><s_cashprice> 50.000</s_cashprice><s_changeprice> 27.000</s_changeprice></s_total>
<s_menu><s_nm> S-Ovaltine</s_nm><s_unitprice> 20,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 20,000</s_price><s_sub><s_nm> 50%</s_nm></s_sub></s_menu><s_sub_total><s_subtotal_price> 18,181</s_subtotal_price><s_tax_price> 1,818</s_tax_price></s_sub_total><s_total><s_total_price> 20,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 80,000</s_changeprice></s_total>
<s_menu><s_nm> M-Carame 1 Black Tea</s_nm><s_unitprice> @28,000</s_unitprice><s_cnt> 1X</s_cnt><s_price> 28,000</s_price><s_sub><s_nm> 70%</s_nm><sep/><s_nm> Less Ice</s_nm></s_sub></s_menu><s_sub_total><s_subtotal_price> 28,000</s_subtotal_price><s_tax_price> 0</s_tax_price></s_sub_total><s_total><s_total_price> 28,000</s_total_price><s_cashprice> 28,000</s_cashprice><s_changeprice> 0</s_changeprice></s_total>
<s_menu><s_nm> BBQ Chicken</s_nm><s_cnt> 1</s_cnt><s_price> 41,000</s_price><s_sub><s_nm> - Sedang</s_nm><s_cnt> 1</s_cnt><s_price> 0</s_price></s_sub></s_menu><s_sub_total><s_subtotal_price> 41,000</s_subtotal_price></s_sub_total><s_total><s_total_price> 41,000</s_total_price><s_cashprice> 50.000</s_cashprice><s_changeprice> 9,000</s_changeprice><s_menuqty_cnt> 1</s_menuqty_cnt></s_total>
<s_menu><s_nm> LE MINERAL</s_nm><s_price> 8,000</s_price></s_menu><s_sub_total><s_subtotal_price> 7,273</s_subtotal_price><s_tax_price> 727</s_tax_price></s_sub_total><s_total><s_total_price> 8,000</s_total_price><s_cashprice> 8,000</s_cashprice></s_total>
<s_menu><s_nm> POTATO SAUSAGE BREAD</s_nm><s_cnt> 1</s_cnt><s_price> 19,000</s_price><sep/><s_nm> OREO GREEN TEA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price><sep/><s_nm> WHITE CHOCO BANANA SPREAD</s_nm><s_cnt> 1</s_cnt><s_price> 52,000</s_price></s_menu><s_total><s_total_price> 123,000</s_total_price><s_creditcardprice> 123,000</s_creditcardprice></s_total>
<s_menu><s_nm> Choco Devil</s_nm><s_cnt> 4</s_cnt><s_price> 63,636</s_price><sep/><s_nm> CP 360 Club Card</s_nm><s_price> -9,545</s_price></s_menu><s_sub_total><s_subtotal_price> 63,636</s_subtotal_price><s_discount_price> -9,545</s_discount_price><s_tax_price> 5,409</s_tax_price></s_sub_total><s_total><s_total_price> 59,500</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 40,500</s_changeprice></s_total>
<s_menu><s_nm> TALAM UNGU</s_nm><s_unitprice> @6500</s_unitprice><s_cnt> 3X</s_cnt><s_discountprice> -7,800</s_discountprice><s_price> 19,500</s_price><sep/><s_nm> MIKA KECIL</s_nm><s_unitprice> @0</s_unitprice><s_cnt> 1X</s_cnt><s_price> 0</s_price></s_menu><s_sub_total><s_subtotal_price> 11,700</s_subtotal_price></s_sub_total><s_total><s_total_price> 11,700</s_total_price><s_cashprice> 20,000</s_cashprice><s_changeprice> 8,300</s_changeprice><s_menuqty_cnt> 4.00xITEMs</s_menuqty_cnt></s_total>
<s_menu><s_nm> Tahu Ikan Oma Giok</s_nm><s_cnt> 1</s_cnt><s_price> 20.000</s_price></s_menu><s_total><s_total_price> 20.000</s_total_price><s_cashprice> 20.000</s_cashprice><s_changeprice> 0</s_changeprice></s_total>
<s_menu><s_nm> Serbu</s_nm><s_cnt> 2</s_cnt><s_price> 40.000</s_price><sep/><s_nm> Choco Peanut Bread</s_nm><s_cnt> 2</s_cnt><s_price> 20.000</s_price></s_menu><s_total><s_total_price> 60.000</s_total_price><s_cashprice> 60.000</s_cashprice><s_changeprice> 0</s_changeprice></s_total>
<s_menu><s_nm> Se'I Sapi Sambal Matah ( R )</s_nm><s_cnt> 1</s_cnt><s_price> 20.000</s_price><sep/><s_nm> Se'I S-pi Lada Hitam (J)</s_nm><s_cnt> 1</s_cnt><s_price> 35.000</s_price><sep/><s_nm> Nasi Putih</s_nm><s_cnt> 2</s_cnt><s_price> 10.000</s_price><sep/><s_nm> Milk Shake Coklat</s_nm><s_cnt> 1</s_cnt><s_price> 16.000</s_price></s_menu><s_sub_total><s_subtotal_price> 81.000</s_subtotal_price><s_tax_price> 8.100</s_tax_price></s_sub_total><s_total><s_total_price> 89.100</s_total_price><s_changeprice> 0</s_changeprice><s_creditcardprice> 89.100</s_creditcardprice></s_total>
<s_menu><s_nm> ES KOPI SUSU</s_nm><s_cnt> 4</s_cnt><s_price> 72.000</s_price></s_menu><s_total><s_total_price> 72.000</s_total_price><s_changeprice> 0</s_changeprice><s_emoneyprice> 72.000</s_emoneyprice></s_total>
<s_menu><s_nm> MINERAL 600 ML</s_nm><s_cnt> 1</s_cnt><s_price> 7,727</s_price><sep/><s_nm> BULGOGI RICE R</s_nm><s_cnt> 1</s_cnt><s_price> 33,636</s_price></s_menu><s_sub_total><s_subtotal_price> 41,364</s_subtotal_price><s_tax_price> 4,136</s_tax_price></s_sub_total><s_total><s_total_price> 45,500</s_total_price><s_cashprice> 50,000</s_cashprice><s_changeprice> -4,500</s_changeprice></s_total>
<s_menu><s_nm> Arem Arem</s_nm><s_unitprice> @12.000</s_unitprice><s_cnt> 2x</s_cnt><s_price> 24.000</s_price><sep/><s_nm> Kroket</s_nm><s_unitprice> @12.000</s_unitprice><s_cnt> 1x</s_cnt><s_price> 12.000</s_price></s_menu><s_sub_total><s_subtotal_price> Rp 36.000</s_subtotal_price><s_tax_price> Rp 3.600</s_tax_price></s_sub_total><s_total><s_total_price> Rp 39.600</s_total_price><s_emoneyprice> Rp 39.600</s_emoneyprice></s_total>
<s_menu><s_nm> Arem Arem</s_nm><s_unitprice> 12.000</s_unitprice><s_cnt> 2</s_cnt><s_price> 24.000</s_price><sep/><s_nm> Pepenero Pastel</s_nm><s_unitprice> 15.000</s_unitprice><s_cnt> 2</s_cnt><s_price> 30.000</s_price></s_menu><s_sub_total><s_subtotal_price> Rp 54.000</s_subtotal_price><s_tax_price> Rp 5.400</s_tax_price></s_sub_total><s_total><s_total_price> Rp 59.400</s_total_price><s_cashprice> Rp 100.000</s_cashprice><s_changeprice> Rp 40.600</s_changeprice></s_total>
<s_menu><s_nm> TT</s_nm><s_unitprice> 20,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 20,000</s_price></s_menu><s_total><s_total_price> 20,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 80,000</s_changeprice></s_total>
<s_menu><s_nm> LEMONADE 16OZ</s_nm><s_unitprice> 20,000</s_unitprice><s_cnt> 1 x</s_cnt><s_price> 20,000</s_price></s_menu><s_sub_total><s_subtotal_price> 20,000</s_subtotal_price></s_sub_total><s_total><s_total_price> 20,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 80,000</s_changeprice></s_total>
<s_menu><s_nm> beef c roll 3pcs</s_nm><s_unitprice> 10,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 10,000</s_price><sep/><s_nm> kaya bred</s_nm><s_unitprice> 15,000</s_unitprice><s_cnt> 1</s_cnt><s_price> 15,000</s_price></s_menu><s_total><s_total_price> 25,000</s_total_price><s_cashprice> 100,000</s_cashprice><s_changeprice> 75,000</s_changeprice><s_menuqty_cnt> 2</s_menuqty_cnt></s_total>
<s_menu><s_nm> FUTAMI 17 GREEN TEA (CLAS</s_nm><s_cnt> 1</s_cnt><s_price> 12,500</s_price><sep/><s_nm> EGG TART</s_nm><s_cnt> 1</s_cnt><s_price> 13,000</s_price><sep/><s_nm> GRAIN CROQUE MONSIEUR</s_nm><s_cnt> 1</s_cnt><s_price> 17,000</s_price></s_menu><s_total><s_total_price> 42,500</s_total_price><s_cashprice> 50,000</s_cashprice><s_changeprice> 7,500</s_changeprice></s_total>
In [ ]:
preds[0]
Out[ ]:
{'menu': [{'nm': 'REAL GANACHE', 'cnt': '1', 'price': '16,500'},
  {'nm': 'EGG TART', 'cnt': '1', 'price': '13,000'},
  {'nm': 'PIZZA TOAST', 'cnt': '1', 'price': '16,000'}],
 'total': {'total_price': '45,500',
  'cashprice': '50,000',
  'changeprice': '4,500'}}

Data Analysis Enhancement with Donut Outputs¶

Agar mempermudah dalam kita menganalisis hasil model Donut maka kita perlu mengubah data generated text tersebut menjadi sebuah tabel.

🎡 Fun Fact: Data dalam format JSON dapat secara otomatis dianggap sebagai dictionary di dalam Python, khususnya dengan library pandas. Ini karena struktur JSON sangat mirip dengan dictionary Python, dengan pasangan key-value yang mudah diakses.

Transform Donut Outputs to Dataframe pandas¶

Kita cukup menggunakan pd.DataFrame.from_dict(), pandas secara otomatis mengonversi struktur JSON tersebut ke dalam bentuk tabel.

In [ ]:
df_preds = pd.DataFrame.from_dict(preds)
df_preds.head()
Out[ ]:
menu total sub_total
0 [{'nm': 'REAL GANACHE', 'cnt': '1', 'price': '... {'total_price': '45,500', 'cashprice': '50,000... NaN
1 {'nm': 'Kopi Susu Kolonel', 'cnt': '1', 'price... {'total_price': '23.000', 'cashprice': '50.000... NaN
2 {'nm': 'S-Ovaltine', 'unitprice': '20,000', 'c... {'total_price': '20,000', 'cashprice': '100,00... {'subtotal_price': '18,181', 'tax_price': '1,8...
3 {'nm': 'M-Carame 1 Black Tea', 'unitprice': '@... {'total_price': '28,000', 'cashprice': '28,000... {'subtotal_price': '28,000', 'tax_price': '0'}
4 {'nm': 'BBQ Chicken', 'cnt': '1', 'price': '41... {'total_price': '41,000', 'cashprice': '50.000... {'subtotal_price': '41,000'}

📈 Insight: Setiap baris pada dataframe kita mewakili ...

🔻 Mari kita cek missing value-nya menggunakan metode isna().sum()

In [ ]:
df_preds.isna().sum()
Out[ ]:
menu         0
total        0
sub_total    9
dtype: int64

📈 Insight: Dalam kasus kali ini kolom sub_total akan diabaikan karena kita akan mencoba mengambil total harga keseluruhan, harga per item, dan jumlah per item saja. Maka dari itu, kita tidak perlu membuat data yang subtotalnya kosong (NaN)

💭 Discussion¶

❓ Apakah menurut Anda, bentuk dataframe data invoice kita sudah cukup rapi/mudah dianalisis?

Kita memiliki data struk yang belum terorganisir dengan baik, dimana informasi menu, total, dan sub-total disajikan dalam satu baris.

Data Wrangling¶

🔻 Untuk memudahkan analisis, kita akan memecah data ini sehingga setiap item pada struk diwakili dalam baris tersendiri, lengkap dengan ID struknya. Pendekatan ini akan mempermudah pengolahan dan analisis data.

  1. Membuat dataframe kosong untuk menjadi tempat pengisian data kita.
In [ ]:
df = pd.DataFrame(columns=['receipt_id', 'nm', 'cnt', 'price', 'total_price'])
df
Out[ ]:
receipt_id nm cnt price total_price
In [ ]:
# cara mengecek jumlah baris pada suatu dataframe
len(df)
Out[ ]:
0
  1. Memasukkan seluruh data pada df_preds ke df dengan memanfaatkan looping .iterrows().

💡 .iterrows() merupakan metode pada objek DataFrame yang menghasilkan pasangan (index, row) untuk DataFrame tersebut

In [ ]:
# mengiterasi setiap rrow dalam df_preds
for index, row in df_preds.iterrows():

    # mengecek apakah field 'menu' adalah list, jika tidak, ubah menjadi list
    menus = row['menu'] if isinstance(row['menu'], list) else [row['menu']]

    # mengiterasi setiap elemen dalam list 'menus'
    for menu in menus:
        # menambahkan row baru ke 'df' dengan informasi dari 'menu' serta tambahan 'total_price' dan 'receipt_id'
        df.loc[len(df)] = {
            **menu,  # unpack semua pasangan key-value dari dictionary 'menu' yang cocok dg nama kolom yg ada
            'total_price': row['total']['total_price'],  # mengambil 'total_price' dari baris saat ini
            'receipt_id': img_filenames[index].split('.')[0]  # mengambil ID struk dari nama file gambar
        }

💡**dict : Melakukan unpack semua pasangan key-value dari dictionary 'dict'

💡df.loc[] : Metode yang digunakan untuk mengakses baris atau kolom tertentu dari DataFrame berdasarkan nama/labelnya

In [ ]:
df
Out[ ]:
receipt_id nm cnt price total_price
0 ID0000 REAL GANACHE 1 16,500 45,500
1 ID0000 EGG TART 1 13,000 45,500
2 ID0000 PIZZA TOAST 1 16,000 45,500
3 ID0001 Kopi Susu Kolonel 1 23.000 23.000
4 ID0002 S-Ovaltine 1 20,000 20,000
5 ID0003 M-Carame 1 Black Tea 1X 28,000 28,000
6 ID0004 BBQ Chicken 1 41,000 41,000
7 ID0005 LE MINERAL NaN 8,000 8,000
8 ID0006 POTATO SAUSAGE BREAD 1 19,000 123,000
9 ID0006 OREO GREEN TEA SPREAD 1 52,000 123,000
10 ID0006 WHITE CHOCO BANANA SPREAD 1 52,000 123,000
11 ID0007 Choco Devil 4 63,636 59,500
12 ID0007 CP 360 Club Card NaN -9,545 59,500
13 ID0008 TALAM UNGU 3X 19,500 11,700
14 ID0008 MIKA KECIL 1X 0 11,700
15 ID0009 Tahu Ikan Oma Giok 1 20.000 20.000
16 ID0010 Serbu 2 40.000 60.000
17 ID0010 Choco Peanut Bread 2 20.000 60.000
18 ID0011 Se'I Sapi Sambal Matah ( R ) 1 20.000 89.100
19 ID0011 Se'I S-pi Lada Hitam (J) 1 35.000 89.100
20 ID0011 Nasi Putih 2 10.000 89.100
21 ID0011 Milk Shake Coklat 1 16.000 89.100
22 ID0012 ES KOPI SUSU 4 72.000 72.000
23 ID0013 MINERAL 600 ML 1 7,727 45,500
24 ID0013 BULGOGI RICE R 1 33,636 45,500
25 ID0014 Arem Arem 2x 24.000 Rp 39.600
26 ID0014 Kroket 1x 12.000 Rp 39.600
27 ID0015 Arem Arem 2 24.000 Rp 59.400
28 ID0015 Pepenero Pastel 2 30.000 Rp 59.400
29 ID0016 TT 1 20,000 20,000
30 ID0017 LEMONADE 16OZ 1 x 20,000 20,000
31 ID0018 beef c roll 3pcs 1 10,000 25,000
32 ID0018 kaya bred 1 15,000 25,000
33 ID0019 FUTAMI 17 GREEN TEA (CLAS 1 12,500 42,500
34 ID0019 EGG TART 1 13,000 42,500
35 ID0019 GRAIN CROQUE MONSIEUR 1 17,000 42,500

📈 Insight: ...

🔻 Mari kita simpan data document extracted yang sudah kita lakukan agar terecord dengan baik

In [ ]:
# menyimpan DataFrame 'df' ke dalam file csv
df.to_csv('receipt_extracted.csv', 
          index=False)  # berarti indeks DataFrame tidak disertakan dalam file csv

Data Cleaning¶

In [ ]:
# cek tipe data
df.info()
<class 'pandas.core.frame.DataFrame'>
Index: 36 entries, 0 to 35
Data columns (total 5 columns):
 #   Column       Non-Null Count  Dtype 
---  ------       --------------  ----- 
 0   receipt_id   36 non-null     object
 1   nm           36 non-null     object
 2   cnt          34 non-null     object
 3   price        36 non-null     object
 4   total_price  36 non-null     object
dtypes: object(5)
memory usage: 1.7+ KB

🔻Nama kolom masih kurang informatif, maka kita akan coba menggantinya menggunakan metode .rename()

In [ ]:
new_columns_name = {
    'nm': 'item_name',
    'cnt': 'quantity'
}

df = df.rename(columns=new_columns_name)
df.head()
Out[ ]:
receipt_id item_name quantity price total_price
0 ID0000 REAL GANACHE 1 16,500 45,500
1 ID0000 EGG TART 1 13,000 45,500
2 ID0000 PIZZA TOAST 1 16,000 45,500
3 ID0001 Kopi Susu Kolonel 1 23.000 23.000
4 ID0002 S-Ovaltine 1 20,000 20,000

🔻 Jika kita lihat kembali kolom price maupun total_price seharusnya memiliki tipe data int/float. Maka dari itu kita perlu melakukan pembersihan ,.

In [ ]:
# fungsi untuk membersihkan string harga dari simbol dan spasi
def clean_price(x):
    return int(x.replace(".", "").replace(",", "").replace("Rp", "").replace(" ", ""))

Mari kita coba aplikasikan fungsi clean_price terhadap salah satu baris pada data kita

In [ ]:
clean_price(df.loc[0, 'price'])
Out[ ]:
16500

❓ Lalu, bagaimana cara mengaplikasikan fungsi tersebut untuk semua baris tanpa kita melakukan satu persatu?

Jawabannya, kita bisa memanfaatkan fungsi .apply()

💡 Fungsi apply memungkinkan kita untuk menjalankan suatu fungsi terhadap setiap elemen dalam sebuah kolom secara otomatis. Contohnya, jika kita ingin membersihkan format harga dalam kolom 'price' di DataFrame df, kita bisa melakukan hal berikut:

In [ ]:
# mengaplikasikan fungsi 'clean_price' ke setiap elemen di kolom 'price' pada 'df'
df['price'] = df['price'].apply(lambda x: clean_price(str(x)))
In [ ]:
# mengaplikasikan fungsi 'clean_price' ke setiap elemen di kolom 'total_price' pada 'df'
df['total_price'] = df['total_price'].apply(lambda x: clean_price(x))

🔻 Selanjutnya, kita akan coba membersihkan kolom quantity seharusnya memiliki tipe data int/float. Maka dari itu kita perlu melakukan pembersihan ,. , serta X

Mari kita buat custom fungsinya terlebih dahulu:

In [ ]:
# membersihkan dan mengkonversi nilai 'x' menjadi float
def clean_quantity(x):
    # jika 'x' bernilai None (tidak ada data), kembalikan 0
    if x is None:
        return 0

    # mengonversi 'x' ke string, menghapus spasi, 'x'/'X', dan ']' dari string tersebut, dan mengembalikan menjadi sebuah angka
    return float(str(x).replace(" ", "").replace("x", "").replace("X", "").replace("]", ""))
In [ ]:
# mengaplikasikan fungsi 'clean_quantity' ke setiap elemen di kolom 'quantity' pada 'df'
df['quantity'] = df['quantity'].apply(lambda x: clean_quantity(x))

🤿 (Opsional) Dive Deeper: 🍩 Advanced Document Extracted Dataframe¶

Misalkan Anda bekerja sebagai data scientist di sebuah supermarket dengan dataset yang mencatat transaksi penjualannya. Dataset ini berisi informasi tentang ID struk (receipt_id), harga total per struk (total_price), dan nama item yang terjual (item_name).

Untuk mendapatkan wawasan yang lebih terstruktur dari data penjualan ini, Anda diminta membuat tabel pivot yang mengelompokkan dan menyajikan data berdasarkan receipt_id, total_price.

In [ ]:
# code here
df_pivot = df.pivot_table(index=['receipt_id', 'total_price', 'item_name'])
df_pivot
Out[ ]:
price quantity
receipt_id total_price item_name
ID0000 45500 EGG TART 13000.0 1.0
PIZZA TOAST 16000.0 1.0
REAL GANACHE 16500.0 1.0
ID0001 23000 Kopi Susu Kolonel 23000.0 1.0
ID0002 20000 S-Ovaltine 20000.0 1.0
ID0003 28000 M-Carame 1 Black Tea 28000.0 1.0
ID0004 41000 BBQ Chicken 41000.0 1.0
ID0005 8000 LE MINERAL 8000.0 NaN
ID0006 123000 OREO GREEN TEA SPREAD 52000.0 1.0
POTATO SAUSAGE BREAD 19000.0 1.0
WHITE CHOCO BANANA SPREAD 52000.0 1.0
ID0007 59500 CP 360 Club Card -9545.0 NaN
Choco Devil 63636.0 4.0
ID0008 11700 MIKA KECIL 0.0 1.0
TALAM UNGU 19500.0 3.0
ID0009 20000 Tahu Ikan Oma Giok 20000.0 1.0
ID0010 60000 Choco Peanut Bread 20000.0 2.0
Serbu 40000.0 2.0
ID0011 89100 Milk Shake Coklat 16000.0 1.0
Nasi Putih 10000.0 2.0
Se'I S-pi Lada Hitam (J) 35000.0 1.0
Se'I Sapi Sambal Matah ( R ) 20000.0 1.0
ID0012 72000 ES KOPI SUSU 72000.0 4.0
ID0013 45500 BULGOGI RICE R 33636.0 1.0
MINERAL 600 ML 7727.0 1.0
ID0014 39600 Arem Arem 24000.0 2.0
Kroket 12000.0 1.0
ID0015 59400 Arem Arem 24000.0 2.0
Pepenero Pastel 30000.0 2.0
ID0016 20000 TT 20000.0 1.0
ID0017 20000 LEMONADE 16OZ 20000.0 1.0
ID0018 25000 beef c roll 3pcs 10000.0 1.0
kaya bred 15000.0 1.0
ID0019 42500 EGG TART 13000.0 1.0
FUTAMI 17 GREEN TEA (CLAS 12500.0 1.0
GRAIN CROQUE MONSIEUR 17000.0 1.0

Integration Gradio for Efficient Scanning¶

🛠️ Gradio adalah sebuah library Python yang digunakan untuk membangun antarmuka web yang mudah digunakan untuk aplikasi berbasis machine learning. Dengan Gradio, kita dapat dengan cepat membuat antarmuka untuk model machine learning sehingga pengguna dapat dengan mudah berinteraksi dengannya tanpa harus memiliki pengetahuan teknis yang mendalam tentang pemrograman atau machine learning.

Berikut adalah contoh web-app menggunakan Gradio:

No description has been provided for this image

Understand the Fundamentals of Gradio¶

gr.Interface() adalah kelas utama dalam Gradio yang digunakan untuk membuat antarmuka pengguna (UI) interaktif untuk model machine learning atau fungsi Python lainnya. Ini memungkinkan developer dengan cepat membuat aplikasi web yang dapat menerima input dari pengguna dan memberikan output atau hasil dari model atau fungsi yang diintegrasikan.

Berikut adalah konsep utama dan parameter gr.Interface:

In [ ]:
import gradio as gr
c:\Users\user\miniconda3\envs\dss_donut\lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm

Main Concept:¶

gr.Interface(input=, output=, fn=, live=)
  • 📥Inputs ("input"): Menentukan jenis input yang akan diterima oleh antarmuka. Contoh: "text", "image", "video", "json", "audio" atau kombinasi "text", "image".

  • 📤Outputs ("output"): Menentukan jenis output yang akan dihasilkan oleh antarmuka. Contoh: "text", "image", "video", "json", "audio" atau kombinasi "text", "image".

  • ⚒️ Function ("fn"): Fungsi Python yang akan dijalankan ketika ada input dari pengguna. Bisa berupa model machine learning atau fungsi khusus lainnya.

  • 🌐 Share ("share"): Boolean, menentukan apakah dibuatkan public link atau tidak (temporary 72 hours deployment). Contoh: True atau False.

Additional gr.Interface Parameters:¶

  • title: Judul antarmuka yang akan ditampilkan di halaman web. Contoh: "My Gradio App".

  • description: Deskripsi antarmuka yang akan ditampilkan di halaman web. Contoh: "A simple app for image classification".

  • sidebar: Menentukan apakah sidebar akan ditampilkan atau tidak. Contoh: True atau False.

  • live: Menentukan apakah antarmuka akan memberikan pembaruan langsung. Contoh: True atau False.

Simple Examples of Usage¶

Berikut adalah contoh sederhana penggunaan gr.Interface:

In [ ]:
import gradio as gr

def my_function(name_text):
    return f"Hello, {name_text}. Nice to meet you!"

test = gr.Interface(fn=my_function, inputs="text", outputs="text", live=True, title="Hello World App")
test.launch()
Running on local URL:  http://127.0.0.1:7861

To create a public link, set `share=True` in `launch()`.
Out[ ]:

📈 Insight: Pada contoh di atas, kita membuat antarmuka yang menerima input teks dan mengembalikan output teks yang merupakan cetakan dari input pengguna.Kita dapat menggabungkan input dan output yang berbeda serta menyesuaikan parameter sesuai kebutuhan aplikasi yang dibangun.

Develop Gradio for Real-time Receipt and Invoice Scanning¶

🔻 Mari kita buat aplikasi aplikasi Gradio yang memungkinkan pengguna melakukan pemindaian dan ekstraksi teks secara real-time pada struktur dokumen seperti kwitansi dan invoice dengan memanfaatkan fungsi doc_to_text yang telah kita rancang sebelumnya.

In [ ]:
model.eval()

demo = gr.Interface(
    fn=doc_to_text,
    inputs="image",
    outputs="json",
    title=f"🗃️ Streamlining Invoice Processing: Document Understanding Transformer",
     description="Extract text from receipts and invoices instantly.",
)
demo.launch(debug=True, share=True)

⚒️ Keunggulan Gradio with Donut:

  1. Efisiensi dan Kepresisian: Mampu mengekstrak teks dari dokumen secara cepat dan akurat, menghemat waktu tanpa perlu proses manual.

  2. Peningkatan Produktivitas: Mempermudah pengelolaan informasi dari kwitansi dan faktur, meningkatkan produktivitas dalam pencatatan keuangan dan pelacakan pengeluaran sehari-hari.

  3. Aksesibilitas dan Kemudahan Penggunaan: Gradio memberikan antarmuka yang intuitif, memungkinkan pengguna mengunggah gambar dokumen dengan cepat tanpa memerlukan keahlian teknis.

Dengan demikian, aplikasi ini tidak hanya berpotensi memberikan solusi teknis, tetapi juga meningkatkan efisiensi dan kenyamanan dalam mengelola informasi dari dokumen struktural seperti kwitansi dan faktur.

(Opsional): Deploy the Gradio¶

✨ Tutorial:

  • https://huggingface.co/blog/gradio-spaces
  • https://www.gradio.app/guides/sharing-your-app

Reference¶

  • Paper:
    • Donut: OCR-free Document Understanding Transformer
    • Attention & Transformer: Attention is All You Need
    • Swin Transformer: Hierarchical Vision Transformer using Shifted Windows
    • Neural Machine Translation by Jointly Learning to Align and Translate

[Additional] 🛠️ Tools Verification¶

  1. Membuat environtmet conda bernama dss_donut dengan python=3.10
  2. Aktifkan environtment dengan conda activate dss_donut
  3. Lakukan pip install -r requirements.txt
In [ ]:
import re
import os
import torch

import gradio as gr
from PIL import Image
from transformers import DonutProcessor, VisionEncoderDecoderModel

processor = DonutProcessor.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")
model = VisionEncoderDecoderModel.from_pretrained("naver-clova-ix/donut-base-finetuned-cord-v2")